/******************************************
Shaoni Sinha (shaoni@uw.edu)
Description: Client side code
******************************************/

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;  // for Socket
import java.net.SocketTimeoutException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;

public class Client {
	
	private String server;
	private int servPort;
	private File clientLog;
	private static boolean isUDP; 
	
	public static void main(String[] args)
	{
		int port = 0;
		String hostname = "";
		boolean hasServerName = false;
		boolean hasPort = false;
		isUDP = false; /* Default protocol is TCP */
		
		/* Check the sanity of arguments */
		if(args.length < 4)
		{
			PrintUsage();
			return;
		}
		/* Parse arguments */
		for(int i = 0; i < args.length; i++)
		{
			if(args[i].equalsIgnoreCase("-p") && (i < args.length-1))
			{
				port = Integer.parseInt(args[i+1]);
				hasPort = true;
			}
			if(args[i].equalsIgnoreCase("-h") && (i < args.length-1))
			{
				hostname = args[i+1];
				hasServerName = true;
			}
			if(args[i].equalsIgnoreCase("-t") && (i < args.length-1))
			{
				if(args[i+1].equalsIgnoreCase("UDP")){
					isUDP = true;
				} 
				else if(args[i+1].equalsIgnoreCase("TCP")){
					isUDP = false;
				} 
				else {
					PrintUsage();
					return;
				}
			}
		}
		if(!hasPort || !hasServerName)
		{
			PrintUsage();
			return;
		}
		
		/* Create requests */
		ArrayList<Request> reqs = createRequestList();
		Client client = new Client(hostname, port);
		
	    /* Send requests one after another to server */
		for(int i = 0; i < reqs.size(); i++){
	    	client.sendRequestToServer(reqs.get(i));
	    }
	    System.out.println(client.GetTimestamp() + " ... Client is done.");
	  }
	
	private static void PrintUsage()
	{
		System.out.println("Usage: java Client -h <hostname> -p <port-number> [-t (UDP | TCP)]");
		System.out.println("          -t communication protocol (default: TCP)");
	}
	
	public Client(String hostname, int theServPort)
	{
		server = hostname;
		servPort = theServPort;
		clientLog = new File("clientlog.txt");
	}

	private void sendRequestToServer(final Request req) {
		
		String requestString = null;
		
		/* Converting the request to a string. */
		if(req.type.equalsIgnoreCase("PUT") && req.args.length == 2){
				requestString = new String ("PUT " + req.args[0] + " " + req.args[1]);					
		} else if(req.type.equalsIgnoreCase("GET") && req.args.length == 1){
				requestString = new String("GET " + req.args[0]);
		} else if (req.type.equalsIgnoreCase("DELETE") && req.args.length == 1){
				requestString = new String("DELETE " + req.args[0]);
		}
		
		/* Communicate with server */
		if(!isUDP) {
			sendRequestToServerWithTCP(requestString);
		}
		else {
			sendRequestToServerWithUDP(requestString);
		}
	}
	
	/* UDP Communication */
	private void sendRequestToServerWithUDP(final String requestString) 
	{
		DatagramSocket aSocket = null;
		try {
			 byte [] sendData = new byte[1024];
			 byte [] recvData = new byte[1024];
			 aSocket = new DatagramSocket();
			 sendData = requestString.getBytes();
			 DatagramPacket request = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(server), servPort);
			 aSocket.send(request);
			 aSocket.setSoTimeout(1000); /* Timeout - 1 second */
			 DatagramPacket reply = new DatagramPacket(recvData, recvData.length);
			 aSocket.receive(reply);
			 String replyFromServer = (new String(reply.getData())).trim();
			 System.out.println(GetTimestamp() + " ... request:'" + requestString + "', response:'"+ replyFromServer + "'.");
		}
		catch (Exception e) {
			String errString = GetTimestamp() + " ... Unresponsive server!\r\n";
			UpdateClientLog(errString);
		}
		finally {
			/* Cleanup */
	  	    if (aSocket != null) {
			    aSocket.close();
		    }
		}
	}
	
	/* TCP/IP Communication */
	private void sendRequestToServerWithTCP(final String requestString) 
	{
	    Socket socket = null;
		DataInputStream dis = null;
		DataOutputStream dos = null;
		try {
			socket = new Socket();
			socket.connect(new InetSocketAddress(server, servPort), 1000 /* timeout - 1 second */);
			System.out.println(GetTimestamp() + " ... Connected to server." );
			dis = new DataInputStream(socket.getInputStream()); 
			dos = new DataOutputStream(socket.getOutputStream()); 
			dos.writeUTF(requestString);
			String replyFromServer = dis.readUTF();
			System.out.println(GetTimestamp() + " ... request:'" + requestString + "', response:'"+ replyFromServer + "'.");
		} catch (SocketTimeoutException e1) {
			String errString = GetTimestamp() + " ... Unresponsive server!\r\n";
			UpdateClientLog(errString);
		} catch (Exception e) {
			String errString = GetTimestamp() + " ... Exception: " + e.getMessage() + "!\r\n";
			UpdateClientLog(errString);
		} finally {
			/* Cleanup */
			try {
				if (dos != null) dos.close();
				if (dis != null) dis.close();
				if (socket != null) socket.close();
			} catch (IOException e) {
				System.out.println("Failure in closing connection!");
				e.printStackTrace();
			}
		}
	}

	/* Create the request list. */    
	private static ArrayList<Request> createRequestList(){
	  ArrayList<Request> reqs = new ArrayList<Request>();
	  
	  /*Put requests.*/
	  reqs.add(new Request("PUT", new String[]{"k1", "v1"}));
	  reqs.add(new Request("PUT", new String[]{"k2", "v2"}));
	  reqs.add(new Request("PUT", new String[]{"k3", "v3"}));
	  reqs.add(new Request("PUT", new String[]{"k4", "v4"}));
	  reqs.add(new Request("PUT", new String[]{"k5", "v5"}));
	  reqs.add(new Request("PUT", new String[]{"k6", "v6"}));
	  
	  /*Get requests.*/
	  reqs.add(new Request("GET", new String[]{"k1"}));
	  reqs.add(new Request("GET", new String[]{"k2"}));
	  reqs.add(new Request("GET", new String[]{"k3"}));
	  reqs.add(new Request("GET", new String[]{"k4"}));
	  reqs.add(new Request("GET", new String[]{"k5"}));
	  reqs.add(new Request("GET", new String[]{"k6"}));
	  
	  /*Delete requests.*/
	  reqs.add(new Request("DELETE", new String[]{"k1"}));
	  reqs.add(new Request("DELETE", new String[]{"k2"}));
	  reqs.add(new Request("DELETE", new String[]{"k3"}));
	  reqs.add(new Request("DELETE", new String[]{"k4"}));
	  reqs.add(new Request("DELETE", new String[]{"k5"}));
	  reqs.add(new Request("DELETE", new String[]{"k6"}));
	  
	  return reqs;
	}
	
	/* Create a timestamp. */    
	private Timestamp GetTimestamp() {
		Date date= new java.util.Date();
		return (new Timestamp(date.getTime()));
	}
	
	/* Update client log. */
	private void UpdateClientLog(String errString) {
		try {
			FileWriter fw = new FileWriter(clientLog, true);
			System.out.print(errString);
			fw.write(errString);
			fw.close();
		} catch (IOException e) {
			System.out.println(GetTimestamp() + " ... Failure to update clientlog!");
			e.printStackTrace();
		}
		return;
	}
}

